import { openApiTypeToTypescriptType } from './utils.js';

export interface ITypeMarkdown {
    /**
     * 获取markdown
     * @param outputs 已打印类型
     * @param level 层次
     */
    markdown(outputs: string[], level: number): TypeMarkdown[];
}

/**
 * markdown行数据
 */
export interface TypeMarkdown {
    /** 名称 */
    name: string;
    /** 层次 */
    level: number;
    /** 参数说明 */
    description: string;
    /** 数据位置 */
    position: 'body' | 'query' | 'path';
    /** 是否必须 */
    required: boolean;
    /** 数据类型 */
    type: string;
}


/**
 * 示例
 */
export interface ISample {
    /**
     * 获取样例
     */
    getSample(outputs: string[]): any;
}

/**
 * 赋值语句
 */
export interface IAssignment {
    /**
     * 获取赋值语句
     * @param assignment
     */
    getAssignment(assignment: string): string;
}

/**
 * 模式
 */
export class Schema implements ITypeMarkdown, ISample {
    /** 名称 */
    readonly title: string;
    /** 属性 */
    readonly properties: SchemaProperty[];

    constructor(name: string) {
        this.title = name;
        this.properties = [];
    }

    init(value: any, schemas: Map<string, Schema>): void {
        let required = value.required || [];
        for (const item of Object.keys(value.properties)) {
            this.properties.push(new SchemaProperty(item, required.includes(item), value.properties[item], schemas));
        }
    }

    /**
     * 类定义
     */
    getClassDefinitionStatement(): string {
        const rets = [];
        rets.push(`export class C${this.title} { `);
        for (let property of this.properties) {
            rets.push(`/** ${property.description} */`);
            rets.push(`${property.code} ${property.required ? '' : '?'}:${property.type.name()};`);
        }
        rets.push(`constructor(value:any){`);
        for (let property of this.properties) {
            rets.push(`this.${property.code} = ` + property.type.getAssignment(`value.${property.code}`) + ';');
        }
        rets.push(`}`);
        rets.push(`}`);
        return rets.join('\n');

    }

    /**
     * 接口定义
     */
    getInterfaceDefinitionStatement(): string {
        const rets = [];
        rets.push(`export interface ${this.title} { `);
        for (let property of this.properties) {
            rets.push(`/** ${property.description} */`);
            rets.push(`${property.code} ${property.required ? '' : '?'} : ${property.type.name()};`);
        }
        rets.push('}');
        return rets.join('\n');
    }

    getSample(outputs: string[]): any {
        if (outputs.includes(this.title)) {
            // 已打印
            return {};
        }
        const data: any = {};
        outputs.push(this.title);
        for (let property of this.properties) {
            data[property.code] = property.getSample(outputs);
        }
        outputs.pop();
        return data;
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        const markdowns: TypeMarkdown[] = [];
        if (outputs.includes(this.title)) {
            // 已打印
            return [];
        }
        for (let property of this.properties) {
            markdowns.push({
                name: property.code,
                level,
                description: property.description ?? '',
                required: property.required,
                position: 'body',
                type: property.type.name()
            });
            outputs.push(this.title);
            markdowns.push(...property.type.markdown(outputs, level + 1));
            outputs.pop();
        }
        return markdowns;
    }
}

export class SchemaProperty implements ISample {
    /** 字段code */
    readonly code: string;
    /** 必填 */
    readonly required: boolean;
    /** 描述 */
    readonly description: string;
    /** 类型 */
    readonly type: IType;

    constructor(code: string, required: boolean, value: any, schemas: Map<string, Schema>) {
        this.code = code;
        this.required = required;
        this.description = value.description;
        this.type = openApiTypeToTypescriptType(schemas, value.type, value.format, value.$ref, value.items);
    }

    getSample(outputs: string[]): any {
        return this.type.getSample(outputs);
    }
}

/**
 * 类型：
 * 基础类型，number,string,boolean,any
 * 数组类型,Array
 * 构造类型
 *    常用构造类型,Date,BigDecimal
 *    引用构造类型
 *    泛型
 */
export interface IType extends ITypeMarkdown, ISample, IAssignment {
    /**
     * 类型
     */
    name(): string;

    /**
     * 基础类型
     */
    basicType(): boolean;
}

export abstract class AbstractType implements IType {

    abstract getSample(outputs: string[]): any ;

    abstract markdown(outputs: string[], level: number): TypeMarkdown[];

    abstract name(): string;

    basicType(): boolean {
        return true;
    }

    relateSchema(schemas: Map<string, Schema>): void {
    }

    getAssignment(assignment: string): string {
        return assignment;
    }
}

export class AnyType extends AbstractType implements IType {

    name(): string {
        return 'any';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(outputs: string[]): any {
        return {};
    }
}


export class NumberType extends AbstractType implements IType {

    name(): string {
        return 'number';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(): any {
        return 1.1;
    }
}

export class IntegerType extends AbstractType implements IType {

    name(): string {
        return 'number';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(): any {
        return 1;
    }
}

export class StringType extends AbstractType implements IType {

    name(): string {
        return 'string';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(): any {
        return 'string';
    }
}

export class BooleanType extends AbstractType implements IType {

    name(): string {
        return 'boolean';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(): any {
        return true;
    }
}


export class ArrayType extends AbstractType implements IType {
    /** item的类型 */
    readonly item: IType;

    constructor(item: IType) {
        super();
        this.item = item;
    }

    name(): string {
        return `Array<${this.item.name()}>`;
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return this.item.markdown(outputs, level);
    }

    getSample(outputs: string[]): any {
        return [this.item.getSample(outputs)];
    }


    getAssignment(assignment: string): string {
        if (this.item.basicType()) {
            return `${assignment}`;
        }
        return `${assignment}.map(v=>${this.item.getAssignment('v')})`;
    }

    basicType(): boolean {
        return false;
    }
}

export class RefType extends AbstractType implements IType {
    private readonly name$: string;
    private readonly schema$: Schema;

    constructor(name: string, schemas: Map<string, Schema>) {
        super();
        this.name$ = name;
        if (!schemas.has(this.name$)) {
            schemas.set(this.name$, new Schema(this.name$));
        }
        this.schema$ = schemas.get(this.name$)!;
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return this.schema$.markdown(outputs, level) || [];
    }


    name(): string {
        return this.name$;
    }

    getSample(outputs: string[]): any {
        return this.schema$.getSample(outputs);
    }

    getAssignment(assignment: string): string {
        return `${assignment} && new ${this.schema$.title}(${assignment})`;
    }

    basicType(): boolean {
        return false;
    }
}

export class DateType extends AbstractType implements IType {
    getSample(outputs: string[]): any {
        return '1970-01-01T00:00:00.000+08';
    }

    relateSchema(schemas: Map<string, Schema>) {
    }

    name(): string {
        return 'Date';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getAssignment(assignment: string): string {
        return `${assignment} || new Date(${assignment})`;
    }

    basicType(): boolean {
        return false;
    }
}

export class DecimalType extends AbstractType implements IType {
    relateSchema(schemas: Map<string, Schema>) {
    }

    name(): string {
        return 'Decimal';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(outputs: string[]): any {
        return 10000.00001;
    }

    getAssignment(assignment: string): string {
        return `${assignment} || new Decimal(${assignment})`;
    }

    basicType(): boolean {
        return false;
    }
}


export class LongType extends AbstractType implements IType {
    relateSchema(schemas: Map<string, Schema>) {
    }

    name(): string {
        return 'string';
    }

    markdown(outputs: string[], level: number): TypeMarkdown[] {
        return [];
    }

    getSample(outputs: string[]): any {
        return '10000';
    }

    getAssignment(statement: string): string {
        return `${statement}`;
    }
}
